
To smooth trajectories, we can use a Kalman filter. The implemented KalmanSmootherCV is based on the assumption of a nearly-constant velocity (CV) model. To use KalmanSmootherCV, the optional dependency StoneSoup needs to be installed.
A closely related type of operation is trajectory generalization which is coverd in a separate notebook.
import pandas as pd
import geopandas as gpd
import movingpandas as mpd
import shapely as shp
import hvplot.pandas
import matplotlib.pyplot as plt
from geopandas import GeoDataFrame, read_file
from shapely.geometry import Point, LineString, Polygon
from datetime import datetime, timedelta
from holoviews import opts, dim
import warnings
warnings.filterwarnings('ignore')
plot_defaults = {'linewidth':5, 'capstyle':'round', 'figsize':(9,3), 'legend':True}
opts.defaults(opts.Overlay(active_tools=['wheel_zoom']))
hvplot_defaults = {'tiles':'CartoLight', 'frame_height':320, 'frame_width':320, 'cmap':'Viridis', 'colorbar':True}
mpd.show_versions()
MovingPandas 0.10.rc1 SYSTEM INFO ----------- python : 3.9.13 | packaged by conda-forge | (main, May 27 2022, 16:50:36) [MSC v.1929 64 bit (AMD64)] executable : H:\miniconda3\envs\mpd-ex\python.exe machine : Windows-10-10.0.19043-SP0 GEOS, GDAL, PROJ INFO --------------------- GEOS : None GEOS lib : None GDAL : 3.5.0 GDAL data dir: None PROJ : 9.0.0 PROJ data dir: H:\miniconda3\pkgs\proj-9.0.0-h1cfcee9_1\Library\share\proj PYTHON DEPENDENCIES ------------------- geopandas : 0.10.2 pandas : 1.4.2 fiona : 1.8.21 numpy : 1.22.4 shapely : 1.8.2 rtree : 1.0.0 pyproj : 3.3.1 matplotlib : 3.5.2 mapclassify: 2.4.3 geopy : 2.2.0 holoviews : 1.14.9 hvplot : 0.8.0 geoviews : 1.9.5 stonesoup : 0.1b9
gdf = read_file('../data/geolife_small.gpkg')
traj_collection = mpd.TrajectoryCollection(gdf, 'trajectory_id', t='t')
split = mpd.ObservationGapSplitter(traj_collection).split(gap=timedelta(minutes=15))
split.plot(column='trajectory_id', **plot_defaults)
<AxesSubplot:>
This smoother operates on the assumption of a nearly-constant velocity (CV) model. The process_noise_std and measurement_noise_std parameters can be used to tune the smoother:
process_noise_std governs the uncertainty associated with the adherence of the new (smooth) trajectories to the CV model assumption; higher values relax the assumption, therefore leading to less-smooth trajectories, and vice-versa.measurement_noise_std controls the assumed error in the original trajectories; higher values dictate that the original trajectories are expected to be noisier (and therefore, less reliable), thus leading to smoother trajectories, and vice-versa.Try tuning these parameters and observe the resulting trajectories:
help(mpd.KalmanSmootherCV)
Help on class KalmanSmootherCV in module movingpandas.trajectory_smoother: class KalmanSmootherCV(TrajectorySmoother) | KalmanSmootherCV(traj) | | Smooths using a Kalman Filter with a Constant Velocity model. | | The Constant Velocity model assumes that the speed between consecutive locations is | nearly constant. For trajectories where ``traj.is_latlon = True`` the smoother | converts to EPSG:3395 (World Mercator) internally to perform filtering and smoothing | | .. note:: | This class makes use of | `Stone Soup <https://stonesoup.readthedocs.io/en/latest/>`_, which is an | optional dependency and not installed by default. To use this class, you need | to install Stone Soup directly | (see `here <https://stonesoup.readthedocs.io/en/latest/#installation>`_). | | Method resolution order: | KalmanSmootherCV | TrajectorySmoother | abc.ABC | builtins.object | | Methods defined here: | | smooth(self, process_noise_std=0.5, measurement_noise_std=1) | Smooth the input Trajectory/TrajectoryCollection | | Parameters | ---------- | process_noise_std: float or sequence of floats of length 2, default is 0.5. | The process (acceleration) noise standard deviation. | | If a sequence (e.g. list, tuple, etc.) is provided, the first index is used | for the x coordinate, while the second is used for the y coordinate. If | ``traj.is_latlon=True`` the values are applied to the easting and northing | coordinate (in EPSG:3395) respectively. | | Alternatively, a single float can be provided, which is assumed to be the | same for both coordinates. | | This governs the uncertainty associated with the adherence of the new | (smooth) trajectories to the CV model assumption; higher values relax | the assumption, therefore leading to less-smooth trajectories, | and vice-versa. | | measurement_noise_std: float or sequence of floats of length 2, default is 1. | The measurement noise standard deviation. | | If a sequence (e.g. list, tuple, etc.) is provided, the first index is used | for the x coordinate, while the second is used for the y coordinate. If | ``traj.is_latlon=True`` the values are applied to the easting and northing | coordinate (in EPSG:3395) respectively. | | Alternatively, a single float can be provided, which is assumed to be the | same for both coordinates. | | This controls the assumed error in the original trajectories; higher values | dictate that the original trajectories are expected to be noisier | (and therefore, less reliable), thus leading to smoother trajectories, | and vice-versa. | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | __abstractmethods__ = frozenset() | | ---------------------------------------------------------------------- | Methods inherited from TrajectorySmoother: | | __init__(self, traj) | Create TrajectorySmoother | | Parameters | ---------- | traj : Trajectory or TrajectoryCollection | | ---------------------------------------------------------------------- | Data descriptors inherited from TrajectorySmoother: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined)
smooth = mpd.KalmanSmootherCV(split).smooth(process_noise_std=0.1, measurement_noise_std=10)
print(smooth)
TrajectoryCollection with 11 trajectories
kwargs = {**hvplot_defaults, 'line_width':4}
(split.hvplot(title='Original Trajectories', **kwargs) +
smooth.hvplot(title='Smooth Trajectories', **kwargs))
kwargs = {**hvplot_defaults, 'c':'speed', 'line_width':7, 'clim':(0,20)}
(split.trajectories[2].hvplot(title='Original Trajectory', **kwargs) +
smooth.trajectories[2].hvplot(title='Smooth Trajectory', **kwargs))
help(mpd.OutlierCleaner)
Help on class OutlierCleaner in module movingpandas.trajectory_cleaner:
class OutlierCleaner(TrajectoryCleaner)
| OutlierCleaner(traj)
|
| Outlier (interquantile range - iqr) based cleaner.
|
| columns : dictionary
| Key - value pairs of columns and alpha (iqr multiplier).
|
| Note: Setting alpha=3 is widely used.
|
| Examples
| --------
|
| >>> mpd.OutlierCleaner(traj).clean({'speed': 3})
|
| Method resolution order:
| OutlierCleaner
| TrajectoryCleaner
| builtins.object
|
| Methods inherited from TrajectoryCleaner:
|
| __init__(self, traj)
| Create TrajectoryCleaner
|
| Parameters
| ----------
| traj : Trajectory or TrajectoryCollection
|
| clean(self, columns)
| Clean the input Trajectory/TrajectoryCollection.
|
| Parameters
| ----------
| columns: dictionary
| Information regarding the columns that will be used to clean the trajectory
| and the accompanying thresholds. ex. - {'speed': 30, 'heading': 360} etc.
|
| Returns
| -------
| Trajectory/TrajectoryCollection
| Cleaned Trajectory or TrajectoryCollection
|
| ----------------------------------------------------------------------
| Data descriptors inherited from TrajectoryCleaner:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
traj = split.trajectories[2]
cleaned = traj.copy()
cleaned.add_speed(overwrite=True)
for i in range(0,10):
cleaned = mpd.OutlierCleaner(cleaned).clean({'speed': 1})
smoothed = mpd.KalmanSmootherCV(cleaned).smooth(process_noise_std=0.1, measurement_noise_std=10)
(traj.hvplot(title='Original Trajectory', **kwargs) +
cleaned.hvplot(title='Cleaned Trajectory', **kwargs) +
smoothed.hvplot(title='Cleaned & Smoothed Trajectory', **kwargs))